<?php
/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Form
 * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */

/** @see Zend_Validate_Interface */
require_once 'Zend/Validate/Interface.php';

/**
 * Zend_Form
 *
 * @category   Zend
 * @package    Zend_Form
 * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 * @version    $Id: Form.php 18951 2009-11-12 16:26:19Z alexander $
 */
class Zend_Form implements Iterator, Countable, Zend_Validate_Interface
{
	/**#@+
	 * Plugin loader type constants
	 */
	const DECORATOR = 'DECORATOR';
	const ELEMENT = 'ELEMENT';
	/**#@-*/

	/**#@+
	 * Method type constants
	 */
	const METHOD_DELETE = 'delete';
	const METHOD_GET    = 'get';
	const METHOD_POST   = 'post';
	const METHOD_PUT    = 'put';
	/**#@-*/

	/**#@+
	 * Encoding type constants
	 */
	const ENCTYPE_URLENCODED = 'application/x-www-form-urlencoded';
	const ENCTYPE_MULTIPART  = 'multipart/form-data';
	/**#@-*/

	/**
	 * Form metadata and attributes
	 * @var array
	 */
	protected $_attribs = array();

	/**
	 * Decorators for rendering
	 * @var array
	 */
	protected $_decorators = array();

	/**
	 * Default display group class
	 * @var string
	 */
	protected $_defaultDisplayGroupClass = 'Zend_Form_DisplayGroup';

	/**
	 * Form description
	 * @var string
	 */
	protected $_description;

	/**
	 * Should we disable loading the default decorators?
	 * @var bool
	 */
	protected $_disableLoadDefaultDecorators = false;

	/**
	 * Display group prefix paths
	 * @var array
	 */
	protected $_displayGroupPrefixPaths = array();

	/**
	 * Groups of elements grouped for display purposes
	 * @var array
	 */
	protected $_displayGroups = array();

	/**
	 * Global decorators to apply to all elements
	 * @var null|array
	 */
	protected $_elementDecorators;

	/**
	 * Prefix paths to use when creating elements
	 * @var array
	 */
	protected $_elementPrefixPaths = array();

	/**
	 * Form elements
	 * @var array
	 */
	protected $_elements = array();

	/**
	 * Array to which elements belong (if any)
	 * @var string
	 */
	protected $_elementsBelongTo;

	/**
	 * Custom form-level error messages
	 * @var array
	 */
	protected $_errorMessages = array();

	/**
	 * Are there errors in the form?
	 * @var bool
	 */
	protected $_errorsExist = false;

	/**
	 * Has the form been manually flagged as an error?
	 * @var bool
	 */
	protected $_errorsForced = false;

	/**
	 * Form order
	 * @var int|null
	 */
	protected $_formOrder;

	/**
	 * Whether or not form elements are members of an array
	 * @var bool
	 */
	protected $_isArray = false;

	/**
	 * Form legend
	 * @var string
	 */
	protected $_legend;

	/**
	 * Plugin loaders
	 * @var array
	 */
	protected $_loaders = array();

	/**
	 * Allowed form methods
	 * @var array
	 */
	protected $_methods = array('delete', 'get', 'post', 'put');

	/**
	 * Order in which to display and iterate elements
	 * @var array
	 */
	protected $_order = array();

	/**
	 * Whether internal order has been updated or not
	 * @var bool
	 */
	protected $_orderUpdated = false;

	/**
	 * Sub form prefix paths
	 * @var array
	 */
	protected $_subFormPrefixPaths = array();

	/**
	 * Sub forms
	 * @var array
	 */
	protected $_subForms = array();

	/**
	 * @var Zend_Translate
	 */
	protected $_translator;

	/**
	 * Global default translation adapter
	 * @var Zend_Translate
	 */
	protected static $_translatorDefault;

	/**
	 * is the translator disabled?
	 * @var bool
	 */
	protected $_translatorDisabled = false;

	/**
	 * @var Zend_View_Interface
	 */
	protected $_view;

	/**
	 * Constructor
	 *
	 * Registers form view helper as decorator
	 *
	 * @param mixed $options
	 * @return void
	 */
	public function __construct($options = null)
	{
		if (is_array($options)) {
			$this->setOptions($options);
		} elseif ($options instanceof Zend_Config) {
			$this->setConfig($options);
		}

		// Extensions...
		$this->init();

		$this->loadDefaultDecorators();
	}

	/**
	 * Clone form object and all children
	 *
	 * @return void
	 */
	public function __clone()
	{
		$elements = array();
		foreach ($this->getElements() as $name => $element) {
			$elements[] = clone $element;
		}
		$this->setElements($elements);

		$subForms = array();
		foreach ($this->getSubForms() as $name => $subForm) {
			$subForms[$name] = clone $subForm;
		}
		$this->setSubForms($subForms);

		$displayGroups = array();
		foreach ($this->_displayGroups as $group)  {
			$clone    = clone $group;
			$elements = array();
			foreach ($clone->getElements() as $name => $e) {
				$elements[] = $this->getElement($name);
			}
			$clone->setElements($elements);
			$displayGroups[] = $clone;
		}
		$this->setDisplayGroups($displayGroups);
	}

	/**
	 * Reset values of form
	 *
	 * @return Zend_Form
	 */
	public function reset()
	{
		foreach ($this->getElements() as $element) {
			$element->setValue(null);
		}
		foreach ($this->getSubForms() as $subForm) {
			$subForm->reset();
		}

		return $this;
	}

	/**
	 * Initialize form (used by extending classes)
	 *
	 * @return void
	 */
	public function init()
	{
	}

	/**
	 * Set form state from options array
	 *
	 * @param  array $options
	 * @return Zend_Form
	 */
	public function setOptions(array $options)
	{
		if (isset($options['prefixPath'])) {
			$this->addPrefixPaths($options['prefixPath']);
			unset($options['prefixPath']);
		}

		if (isset($options['elementPrefixPath'])) {
			$this->addElementPrefixPaths($options['elementPrefixPath']);
			unset($options['elementPrefixPath']);
		}

		if (isset($options['displayGroupPrefixPath'])) {
			$this->addDisplayGroupPrefixPaths($options['displayGroupPrefixPath']);
			unset($options['displayGroupPrefixPath']);
		}

		if (isset($options['elementDecorators'])) {
			$this->_elementDecorators = $options['elementDecorators'];
			unset($options['elementDecorators']);
		}

		if (isset($options['elements'])) {
			$this->setElements($options['elements']);
			unset($options['elements']);
		}

		if (isset($options['defaultDisplayGroupClass'])) {
			$this->setDefaultDisplayGroupClass($options['defaultDisplayGroupClass']);
			unset($options['defaultDisplayGroupClass']);
		}

		if (isset($options['displayGroupDecorators'])) {
			$displayGroupDecorators = $options['displayGroupDecorators'];
			unset($options['displayGroupDecorators']);
		}

		if (isset($options['elementsBelongTo'])) {
			$elementsBelongTo = $options['elementsBelongTo'];
			unset($options['elementsBelongTo']);
		}

		if (isset($options['attribs'])) {
			$this->addAttribs($options['attribs']);
			unset($options['attribs']);
		}

		$forbidden = array(
            'Options', 'Config', 'PluginLoader', 'SubForms', 'View', 'Translator',
            'Attrib', 'Default',
		);

		foreach ($options as $key => $value) {
			$normalized = ucfirst($key);
			if (in_array($normalized, $forbidden)) {
				continue;
			}

			$method = 'set' . $normalized;
			if (method_exists($this, $method)) {
				$this->$method($value);
			} else {
				$this->setAttrib($key, $value);
			}
		}

		if (isset($displayGroupDecorators)) {
			$this->setDisplayGroupDecorators($displayGroupDecorators);
		}

		if (isset($elementsBelongTo)) {
			$this->setElementsBelongTo($elementsBelongTo);
		}

		return $this;
	}

	/**
	 * Set form state from config object
	 *
	 * @param  Zend_Config $config
	 * @return Zend_Form
	 */
	public function setConfig(Zend_Config $config)
	{
		return $this->setOptions($config->toArray());
	}


	// Loaders

	/**
	 * Set plugin loaders for use with decorators and elements
	 *
	 * @param  Zend_Loader_PluginLoader_Interface $loader
	 * @param  string $type 'decorator' or 'element'
	 * @return Zend_Form
	 * @throws Zend_Form_Exception on invalid type
	 */
	public function setPluginLoader(Zend_Loader_PluginLoader_Interface $loader, $type = null)
	{
		$type = strtoupper($type);
		switch ($type) {
			case self::DECORATOR:
			case self::ELEMENT:
				$this->_loaders[$type] = $loader;
				return $this;
			default:
				require_once 'Zend/Form/Exception.php';
				throw new Zend_Form_Exception(sprintf('Invalid type "%s" provided to setPluginLoader()', $type));
		}
	}

	/**
	 * Retrieve plugin loader for given type
	 *
	 * $type may be one of:
	 * - decorator
	 * - element
	 *
	 * If a plugin loader does not exist for the given type, defaults are
	 * created.
	 *
	 * @param  string $type
	 * @return Zend_Loader_PluginLoader_Interface
	 */
	public function getPluginLoader($type = null)
	{
		$type = strtoupper($type);
		if (!isset($this->_loaders[$type])) {
			switch ($type) {
				case self::DECORATOR:
					$prefixSegment = 'Form_Decorator';
					$pathSegment   = 'Form/Decorator';
					break;
				case self::ELEMENT:
					$prefixSegment = 'Form_Element';
					$pathSegment   = 'Form/Element';
					break;
				default:
					require_once 'Zend/Form/Exception.php';
					throw new Zend_Form_Exception(sprintf('Invalid type "%s" provided to getPluginLoader()', $type));
			}

			require_once 'Zend/Loader/PluginLoader.php';
			$this->_loaders[$type] = new Zend_Loader_PluginLoader(
			array('Zend_' . $prefixSegment . '_' => 'Zend/' . $pathSegment . '/')
			);
		}

		return $this->_loaders[$type];
	}

	/**
	 * Add prefix path for plugin loader
	 *
	 * If no $type specified, assumes it is a base path for both filters and
	 * validators, and sets each according to the following rules:
	 * - decorators: $prefix = $prefix . '_Decorator'
	 * - elements: $prefix = $prefix . '_Element'
	 *
	 * Otherwise, the path prefix is set on the appropriate plugin loader.
	 *
	 * If $type is 'decorators', sets the path in the decorator plugin loader
	 * for all elements. Additionally, if no $type is provided,
	 * {@link Zend_Form_Element::addPrefixPath()} is called on each element.
	 *
	 * @param  string $prefix
	 * @param  string $path
	 * @param  string $type
	 * @return Zend_Form
	 * @throws Zend_Form_Exception for invalid type
	 */
	public function addPrefixPath($prefix, $path, $type = null)
	{
		$type = strtoupper($type);
		switch ($type) {
			case self::DECORATOR:
			case self::ELEMENT:
				$loader = $this->getPluginLoader($type);
				$loader->addPrefixPath($prefix, $path);
				return $this;
			case null:
				$prefix = rtrim($prefix, '_');
				$path   = rtrim($path, DIRECTORY_SEPARATOR);
				foreach (array(self::DECORATOR, self::ELEMENT) as $type) {
					$cType        = ucfirst(strtolower($type));
					$pluginPath   = $path . DIRECTORY_SEPARATOR . $cType . DIRECTORY_SEPARATOR;
					$pluginPrefix = $prefix . '_' . $cType;
					$loader       = $this->getPluginLoader($type);
					$loader->addPrefixPath($pluginPrefix, $pluginPath);
				}
				return $this;
			default:
				require_once 'Zend/Form/Exception.php';
				throw new Zend_Form_Exception(sprintf('Invalid type "%s" provided to getPluginLoader()', $type));
		}
	}

	/**
	 * Add many prefix paths at once
	 *
	 * @param  array $spec
	 * @return Zend_Form
	 */
	public function addPrefixPaths(array $spec)
	{
		if (isset($spec['prefix']) && isset($spec['path'])) {
			return $this->addPrefixPath($spec['prefix'], $spec['path']);
		}
		foreach ($spec as $type => $paths) {
			if (is_numeric($type) && is_array($paths)) {
				$type = null;
				if (isset($paths['prefix']) && isset($paths['path'])) {
					if (isset($paths['type'])) {
						$type = $paths['type'];
					}
					$this->addPrefixPath($paths['prefix'], $paths['path'], $type);
				}
			} elseif (!is_numeric($type)) {
				if (!isset($paths['prefix']) || !isset($paths['path'])) {
					continue;
				}
				$this->addPrefixPath($paths['prefix'], $paths['path'], $type);
			}
		}
		return $this;
	}

	/**
	 * Add prefix path for all elements
	 *
	 * @param  string $prefix
	 * @param  string $path
	 * @param  string $type
	 * @return Zend_Form
	 */
	public function addElementPrefixPath($prefix, $path, $type = null)
	{
		$this->_elementPrefixPaths[] = array(
            'prefix' => $prefix,
            'path'   => $path,
            'type'   => $type,
		);

		foreach ($this->getElements() as $element) {
			$element->addPrefixPath($prefix, $path, $type);
		}

		foreach ($this->getSubForms() as $subForm) {
			$subForm->addElementPrefixPath($prefix, $path, $type);
		}

		return $this;
	}

	/**
	 * Add prefix paths for all elements
	 *
	 * @param  array $spec
	 * @return Zend_Form
	 */
	public function addElementPrefixPaths(array $spec)
	{
		$this->_elementPrefixPaths = $this->_elementPrefixPaths + $spec;

		foreach ($this->getElements() as $element) {
			$element->addPrefixPaths($spec);
		}

		return $this;
	}

	/**
	 * Add prefix path for all display groups
	 *
	 * @param  string $prefix
	 * @param  string $path
	 * @return Zend_Form
	 */
	public function addDisplayGroupPrefixPath($prefix, $path)
	{
		$this->_displayGroupPrefixPaths[] = array(
            'prefix' => $prefix,
            'path'   => $path,
		);

		foreach ($this->getDisplayGroups() as $group) {
			$group->addPrefixPath($prefix, $path);
		}

		return $this;
	}

	/**
	 * Add multiple display group prefix paths at once
	 *
	 * @param  array $spec
	 * @return Zend_Form
	 */
	public function addDisplayGroupPrefixPaths(array $spec)
	{
		foreach ($spec as $key => $value) {
			if (is_string($value) && !is_numeric($key)) {
				$this->addDisplayGroupPrefixPath($key, $value);
				continue;
			}

			if (is_string($value) && is_numeric($key)) {
				continue;
			}

			if (is_array($value)) {
				$count = count($value);
				if (array_keys($value) === range(0, $count - 1)) {
					if ($count < 2) {
						continue;
					}
					$prefix = array_shift($value);
					$path   = array_shift($value);
					$this->addDisplayGroupPrefixPath($prefix, $path);
					continue;
				}
				if (array_key_exists('prefix', $value) && array_key_exists('path', $value)) {
					$this->addDisplayGroupPrefixPath($value['prefix'], $value['path']);
				}
			}
		}
		return $this;
	}

	// Form metadata:

	/**
	 * Set form attribute
	 *
	 * @param  string $key
	 * @param  mixed $value
	 * @return Zend_Form
	 */
	public function setAttrib($key, $value)
	{
		$key = (string) $key;
		$this->_attribs[$key] = $value;
		return $this;
	}

	/**
	 * Add multiple form attributes at once
	 *
	 * @param  array $attribs
	 * @return Zend_Form
	 */
	public function addAttribs(array $attribs)
	{
		foreach ($attribs as $key => $value) {
			$this->setAttrib($key, $value);
		}
		return $this;
	}

	/**
	 * Set multiple form attributes at once
	 *
	 * Overwrites any previously set attributes.
	 *
	 * @param  array $attribs
	 * @return Zend_Form
	 */
	public function setAttribs(array $attribs)
	{
		$this->clearAttribs();
		return $this->addAttribs($attribs);
	}

	/**
	 * Retrieve a single form attribute
	 *
	 * @param  string $key
	 * @return mixed
	 */
	public function getAttrib($key)
	{
		$key = (string) $key;
		if (!isset($this->_attribs[$key])) {
			return null;
		}

		return $this->_attribs[$key];
	}

	/**
	 * Retrieve all form attributes/metadata
	 *
	 * @return array
	 */
	public function getAttribs()
	{
		return $this->_attribs;
	}

	/**
	 * Remove attribute
	 *
	 * @param  string $key
	 * @return bool
	 */
	public function removeAttrib($key)
	{
		if (isset($this->_attribs[$key])) {
			unset($this->_attribs[$key]);
			return true;
		}

		return false;
	}

	/**
	 * Clear all form attributes
	 *
	 * @return Zend_Form
	 */
	public function clearAttribs()
	{
		$this->_attribs = array();
		return $this;
	}

	/**
	 * Set form action
	 *
	 * @param  string $action
	 * @return Zend_Form
	 */
	public function setAction($action)
	{
		return $this->setAttrib('action', (string) $action);
	}

	/**
	 * Get form action
	 *
	 * Sets default to '' if not set.
	 *
	 * @return string
	 */
	public function getAction()
	{
		$action = $this->getAttrib('action');
		if (null === $action) {
			$action = '';
			$this->setAction($action);
		}
		return $action;
	}

	/**
	 * Set form method
	 *
	 * Only values in {@link $_methods()} allowed
	 *
	 * @param  string $method
	 * @return Zend_Form
	 * @throws Zend_Form_Exception
	 */
	public function setMethod($method)
	{
		$method = strtolower($method);
		if (!in_array($method, $this->_methods)) {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception(sprintf('"%s" is an invalid form method', $method));
		}
		$this->setAttrib('method', $method);
		return $this;
	}

	/**
	 * Retrieve form method
	 *
	 * @return string
	 */
	public function getMethod()
	{
		if (null === ($method = $this->getAttrib('method'))) {
			$method = self::METHOD_POST;
			$this->setAttrib('method', $method);
		}
		return strtolower($method);
	}

	/**
	 * Set encoding type
	 *
	 * @param  string $value
	 * @return Zend_Form
	 */
	public function setEnctype($value)
	{
		$this->setAttrib('enctype', $value);
		return $this;
	}

	/**
	 * Get encoding type
	 *
	 * @return string
	 */
	public function getEnctype()
	{
		if (null === ($enctype = $this->getAttrib('enctype'))) {
			$enctype = self::ENCTYPE_URLENCODED;
			$this->setAttrib('enctype', $enctype);
		}
		return $this->getAttrib('enctype');
	}

	/**
	 * Filter a name to only allow valid variable characters
	 *
	 * @param  string $value
	 * @param  bool $allowBrackets
	 * @return string
	 */
	public function filterName($value, $allowBrackets = false)
	{
		$charset = '^a-zA-Z0-9_\x7f-\xff';
		if ($allowBrackets) {
			$charset .= '\[\]';
		}
		return preg_replace('/[' . $charset . ']/', '', (string) $value);
	}

	/**
	 * Set form name
	 *
	 * @param  string $name
	 * @return Zend_Form
	 */
	public function setName($name)
	{
		$name = $this->filterName($name);
		if (('0' !== $name) && empty($name)) {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception('Invalid name provided; must contain only valid variable characters and be non-empty');
		}

		return $this->setAttrib('name', $name);
	}

	/**
	 * Get name attribute
	 *
	 * @return null|string
	 */
	public function getName()
	{
		return $this->getAttrib('name');
	}

	/**
	 * Get fully qualified name
	 *
	 * Places name as subitem of array and/or appends brackets.
	 *
	 * @return string
	 */
	public function getFullyQualifiedName()
	{
		return $this->getName();
	}

	/**
	 * Get element id
	 *
	 * @return string
	 */
	public function getId()
	{
		if (null !== ($id = $this->getAttrib('id'))) {
			return $id;
		}

		$id = $this->getFullyQualifiedName();

		// Bail early if no array notation detected
		if (!strstr($id, '[')) {
			return $id;
		}

		// Strip array notation
		if ('[]' == substr($id, -2)) {
			$id = substr($id, 0, strlen($id) - 2);
		}
		$id = str_replace('][', '-', $id);
		$id = str_replace(array(']', '['), '-', $id);
		$id = trim($id, '-');

		return $id;
	}

	/**
	 * Set form legend
	 *
	 * @param  string $value
	 * @return Zend_Form
	 */
	public function setLegend($value)
	{
		$this->_legend = (string) $value;
		return $this;
	}

	/**
	 * Get form legend
	 *
	 * @return string
	 */
	public function getLegend()
	{
		return $this->_legend;
	}

	/**
	 * Set form description
	 *
	 * @param  string $value
	 * @return Zend_Form
	 */
	public function setDescription($value)
	{
		$this->_description = (string) $value;
		return $this;
	}

	/**
	 * Retrieve form description
	 *
	 * @return string
	 */
	public function getDescription()
	{
		return $this->_description;
	}

	/**
	 * Set form order
	 *
	 * @param  int $index
	 * @return Zend_Form
	 */
	public function setOrder($index)
	{
		$this->_formOrder = (int) $index;
		return $this;
	}

	/**
	 * Get form order
	 *
	 * @return int|null
	 */
	public function getOrder()
	{
		return $this->_formOrder;
	}

	// Element interaction:

	/**
	 * Add a new element
	 *
	 * $element may be either a string element type, or an object of type
	 * Zend_Form_Element. If a string element type is provided, $name must be
	 * provided, and $options may be optionally provided for configuring the
	 * element.
	 *
	 * If a Zend_Form_Element is provided, $name may be optionally provided,
	 * and any provided $options will be ignored.
	 *
	 * @param  string|Zend_Form_Element $element
	 * @param  string $name
	 * @param  array|Zend_Config $options
	 * @return Zend_Form
	 */
	public function addElement($element, $name = null, $options = null)
	{
		if (is_string($element)) {
			if (null === $name) {
				require_once 'Zend/Form/Exception.php';
				throw new Zend_Form_Exception('Elements specified by string must have an accompanying name');
			}

			if (is_array($this->_elementDecorators)) {
				if (null === $options) {
					$options = array('decorators' => $this->_elementDecorators);
				} elseif ($options instanceof Zend_Config) {
					$options = $options->toArray();
				}
				if (is_array($options)
				&& !array_key_exists('decorators', $options)
				) {
					$options['decorators'] = $this->_elementDecorators;
				}
			}

			$this->_elements[$name] = $this->createElement($element, $name, $options);
		} elseif ($element instanceof Zend_Form_Element) {
			$prefixPaths              = array();
			$prefixPaths['decorator'] = $this->getPluginLoader('decorator')->getPaths();
			if (!empty($this->_elementPrefixPaths)) {
				$prefixPaths = array_merge($prefixPaths, $this->_elementPrefixPaths);
			}

			if (null === $name) {
				$name = $element->getName();
			}

			$this->_elements[$name] = $element;
			$this->_elements[$name]->addPrefixPaths($prefixPaths);
		}

		$this->_order[$name] = $this->_elements[$name]->getOrder();
		$this->_orderUpdated = true;
		$this->_setElementsBelongTo($name);

		return $this;
	}

	/**
	 * Create an element
	 *
	 * Acts as a factory for creating elements. Elements created with this
	 * method will not be attached to the form, but will contain element
	 * settings as specified in the form object (including plugin loader
	 * prefix paths, default decorators, etc.).
	 *
	 * @param  string $type
	 * @param  string $name
	 * @param  array|Zend_Config $options
	 * @return Zend_Form_Element
	 */
	public function createElement($type, $name, $options = null)
	{
		if (!is_string($type)) {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception('Element type must be a string indicating type');
		}

		if (!is_string($name)) {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception('Element name must be a string');
		}

		$prefixPaths              = array();
		$prefixPaths['decorator'] = $this->getPluginLoader('decorator')->getPaths();
		if (!empty($this->_elementPrefixPaths)) {
			$prefixPaths = array_merge($prefixPaths, $this->_elementPrefixPaths);
		}

		if ($options instanceof Zend_Config) {
			$options = $options->toArray();
		}

		if ((null === $options) || !is_array($options)) {
			$options = array('prefixPath' => $prefixPaths);
		} elseif (is_array($options)) {
			if (array_key_exists('prefixPath', $options)) {
				$options['prefixPath'] = array_merge($prefixPaths, $options['prefixPath']);
			} else {
				$options['prefixPath'] = $prefixPaths;
			}
		}

		$class = $this->getPluginLoader(self::ELEMENT)->load($type);
		$element = new $class($name, $options);

		return $element;
	}

	/**
	 * Add multiple elements at once
	 *
	 * @param  array $elements
	 * @return Zend_Form
	 */
	public function addElements(array $elements)
	{
		foreach ($elements as $key => $spec) {
			$name = null;
			if (!is_numeric($key)) {
				$name = $key;
			}

			if (is_string($spec) || ($spec instanceof Zend_Form_Element)) {
				$this->addElement($spec, $name);
				continue;
			}

			if (is_array($spec)) {
				$argc = count($spec);
				$options = array();
				if (isset($spec['type'])) {
					$type = $spec['type'];
					if (isset($spec['name'])) {
						$name = $spec['name'];
					}
					if (isset($spec['options'])) {
						$options = $spec['options'];
					}
					$this->addElement($type, $name, $options);
				} else {
					switch ($argc) {
						case 0:
							continue;
						case (1 <= $argc):
							$type = array_shift($spec);
						case (2 <= $argc):
							if (null === $name) {
								$name = array_shift($spec);
							} else {
								$options = array_shift($spec);
							}
						case (3 <= $argc):
							if (empty($options)) {
								$options = array_shift($spec);
							}
						default:
							$this->addElement($type, $name, $options);
					}
				}
			}
		}
		return $this;
	}

	/**
	 * Set form elements (overwrites existing elements)
	 *
	 * @param  array $elements
	 * @return Zend_Form
	 */
	public function setElements(array $elements)
	{
		$this->clearElements();
		return $this->addElements($elements);
	}

	/**
	 * Retrieve a single element
	 *
	 * @param  string $name
	 * @return Zend_Form_Element|null
	 */
	public function getElement($name)
	{
		if (array_key_exists($name, $this->_elements)) {
			return $this->_elements[$name];
		}
		return null;
	}

	/**
	 * Retrieve all elements
	 *
	 * @return array
	 */
	public function getElements()
	{
		return $this->_elements;
	}

	/**
	 * Remove element
	 *
	 * @param  string $name
	 * @return boolean
	 */
	public function removeElement($name)
	{
		$name = (string) $name;
		if (isset($this->_elements[$name])) {
			unset($this->_elements[$name]);
			if (array_key_exists($name, $this->_order)) {
				unset($this->_order[$name]);
				$this->_orderUpdated = true;
			} else {
				foreach ($this->_displayGroups as $group) {
					if (null !== $group->getElement($name)) {
						$group->removeElement($name);
					}
				}
			}
			return true;
		}

		return false;
	}

	/**
	 * Remove all form elements
	 *
	 * @return Zend_Form
	 */
	public function clearElements()
	{
		foreach (array_keys($this->_elements) as $key) {
			if (array_key_exists($key, $this->_order)) {
				unset($this->_order[$key]);
			}
		}
		$this->_elements     = array();
		$this->_orderUpdated = true;
		return $this;
	}

	/**
	 * Set default values for elements
	 *
	 * Sets values for all elements specified in the array of $defaults.
	 *
	 * @param  array $defaults
	 * @return Zend_Form
	 */
	public function setDefaults(array $defaults)
	{
		foreach ($this->getElements() as $name => $element) {
			if (array_key_exists($name, $defaults)) {
				$this->setDefault($name, $defaults[$name]);
			}
		}
		foreach ($this->getSubForms() as $name => $form) {
			if (array_key_exists($name, $defaults)) {
				$form->setDefaults($defaults[$name]);
			} else {
				$form->setDefaults($defaults);
			}
		}
		return $this;
	}

	/**
	 * Set default value for an element
	 *
	 * @param  string $name
	 * @param  mixed $value
	 * @return Zend_Form
	 */
	public function setDefault($name, $value)
	{
		$name = (string) $name;
		if ($element = $this->getElement($name)) {
			$element->setValue($value);
		} else {
			if (is_scalar($value)) {
				foreach ($this->getSubForms() as $subForm) {
					$subForm->setDefault($name, $value);
				}
			} elseif (is_array($value) && ($subForm = $this->getSubForm($name))) {
				$subForm->setDefaults($value);
			}
		}
		return $this;
	}

	/**
	 * Retrieve value for single element
	 *
	 * @param  string $name
	 * @return mixed
	 */
	public function getValue($name)
	{
		if ($element = $this->getElement($name)) {
			return $element->getValue();
		}

		if ($subForm = $this->getSubForm($name)) {
			return $subForm->getValues(true);
		}

		foreach ($this->getSubForms() as $subForm) {
			if ($name == $subForm->getElementsBelongTo()) {
				return $subForm->getValues(true);
			}
		}
		return null;
	}

	/**
	 * Retrieve all form element values
	 *
	 * @param  bool $suppressArrayNotation
	 * @return array
	 */
	public function getValues($suppressArrayNotation = false)
	{
		$values = array();
		foreach ($this->getElements() as $key => $element) {
			if (!$element->getIgnore()) {
				$values[$key] = $element->getValue();
			}
		}
		foreach ($this->getSubForms() as $key => $subForm) {
			$fValues = $this->_attachToArray($subForm->getValues(true), $subForm->getElementsBelongTo());
			$values = array_merge($values, $fValues);
		}

		if (!$suppressArrayNotation && $this->isArray()) {
			$values = $this->_attachToArray($values, $this->getElementsBelongTo());
		}

		return $values;
	}

	/**
	 * Get unfiltered element value
	 *
	 * @param  string $name
	 * @return mixed
	 */
	public function getUnfilteredValue($name)
	{
		if ($element = $this->getElement($name)) {
			return $element->getUnfilteredValue();
		}
		return null;
	}

	/**
	 * Retrieve all unfiltered element values
	 *
	 * @return array
	 */
	public function getUnfilteredValues()
	{
		$values = array();
		foreach ($this->getElements() as $key => $element) {
			$values[$key] = $element->getUnfilteredValue();
		}

		return $values;
	}

	/**
	 * Set all elements' filters
	 *
	 * @param  array $filters
	 * @return Zend_Form
	 */
	public function setElementFilters(array $filters)
	{
		foreach ($this->getElements() as $element) {
			$element->setFilters($filters);
		}
		return $this;
	}

	/**
	 * Set name of array elements belong to
	 *
	 * @param  string $array
	 * @return Zend_Form
	 */
	public function setElementsBelongTo($array)
	{
		$origName = $this->getElementsBelongTo();
		$name = $this->filterName($array, true);
		if (empty($name)) {
			$name = null;
		}
		$this->_elementsBelongTo = $name;

		if (null === $name) {
			$this->setIsArray(false);
			if (null !== $origName) {
				$this->_setElementsBelongTo();
			}
		} else {
			$this->setIsArray(true);
			$this->_setElementsBelongTo();
		}

		return $this;
	}

	/**
	 * Set array to which elements belong
	 *
	 * @param  string $name Element name
	 * @return void
	 */
	protected function _setElementsBelongTo($name = null)
	{
		$array = $this->getElementsBelongTo();

		if (null === $array) {
			return;
		}

		if (null === $name) {
			foreach ($this->getElements() as $element) {
				$element->setBelongsTo($array);
			}
		} else {
			if (null !== ($element = $this->getElement($name))) {
				$element->setBelongsTo($array);
			}
		}
	}

	/**
	 * Get name of array elements belong to
	 *
	 * @return string|null
	 */
	public function getElementsBelongTo()
	{
		if ((null === $this->_elementsBelongTo) && $this->isArray()) {
			$name = $this->getName();
			if (!empty($name)) {
				return $name;
			}
		}
		return $this->_elementsBelongTo;
	}

	/**
	 * Set flag indicating elements belong to array
	 *
	 * @param  bool $flag Value of flag
	 * @return Zend_Form
	 */
	public function setIsArray($flag)
	{
		$this->_isArray = (bool) $flag;
		return $this;
	}

	/**
	 * Get flag indicating if elements belong to an array
	 *
	 * @return bool
	 */
	public function isArray()
	{
		return $this->_isArray;
	}

	// Element groups:

	/**
	 * Add a form group/subform
	 *
	 * @param  Zend_Form $form
	 * @param  string $name
	 * @param  int $order
	 * @return Zend_Form
	 */
	public function addSubForm(Zend_Form $form, $name, $order = null)
	{
		$name = (string) $name;
		foreach ($this->_loaders as $type => $loader) {
			$loaderPaths = $loader->getPaths();
			foreach ($loaderPaths as $prefix => $paths) {
				foreach ($paths as $path) {
					$form->addPrefixPath($prefix, $path, $type);
				}
			}
		}

		if (!empty($this->_elementPrefixPaths)) {
			foreach ($this->_elementPrefixPaths as $spec) {
				list($prefix, $path, $type) = array_values($spec);
				$form->addElementPrefixPath($prefix, $path, $type);
			}
		}

		if (!empty($this->_displayGroupPrefixPaths)) {
			foreach ($this->_displayGroupPrefixPaths as $spec) {
				list($prefix, $path) = array_values($spec);
				$form->addDisplayGroupPrefixPath($prefix, $path);
			}
		}

		if (null !== $order) {
			$form->setOrder($order);
		}

		$form->setName($name);
		$this->_subForms[$name] = $form;
		$this->_order[$name]    = $order;
		$this->_orderUpdated    = true;
		return $this;
	}

	/**
	 * Add multiple form subForms/subforms at once
	 *
	 * @param  array $subForms
	 * @return Zend_Form
	 */
	public function addSubForms(array $subForms)
	{
		foreach ($subForms as $key => $spec) {
			$name = null;
			if (!is_numeric($key)) {
				$name = $key;
			}

			if ($spec instanceof Zend_Form) {
				$this->addSubForm($spec, $name);
				continue;
			}

			if (is_array($spec)) {
				$argc  = count($spec);
				$order = null;
				switch ($argc) {
					case 0:
						continue;
					case (1 <= $argc):
						$subForm = array_shift($spec);
					case (2 <= $argc):
						$name  = array_shift($spec);
					case (3 <= $argc):
						$order = array_shift($spec);
					default:
						$this->addSubForm($subForm, $name, $order);
				}
			}
		}
		return $this;
	}

	/**
	 * Set multiple form subForms/subforms (overwrites)
	 *
	 * @param  array $subForms
	 * @return Zend_Form
	 */
	public function setSubForms(array $subForms)
	{
		$this->clearSubForms();
		return $this->addSubForms($subForms);
	}

	/**
	 * Retrieve a form subForm/subform
	 *
	 * @param  string $name
	 * @return Zend_Form|null
	 */
	public function getSubForm($name)
	{
		$name = (string) $name;
		if (isset($this->_subForms[$name])) {
			return $this->_subForms[$name];
		}
		return null;
	}

	/**
	 * Retrieve all form subForms/subforms
	 *
	 * @return array
	 */
	public function getSubForms()
	{
		return $this->_subForms;
	}

	/**
	 * Remove form subForm/subform
	 *
	 * @param  string $name
	 * @return boolean
	 */
	public function removeSubForm($name)
	{
		$name = (string) $name;
		if (array_key_exists($name, $this->_subForms)) {
			unset($this->_subForms[$name]);
			if (array_key_exists($name, $this->_order)) {
				unset($this->_order[$name]);
				$this->_orderUpdated = true;
			}
			return true;
		}

		return false;
	}

	/**
	 * Remove all form subForms/subforms
	 *
	 * @return Zend_Form
	 */
	public function clearSubForms()
	{
		foreach (array_keys($this->_subForms) as $key) {
			if (array_key_exists($key, $this->_order)) {
				unset($this->_order[$key]);
			}
		}
		$this->_subForms     = array();
		$this->_orderUpdated = true;
		return $this;
	}


	// Display groups:

	/**
	 * Set default display group class
	 *
	 * @param  string $class
	 * @return Zend_Form
	 */
	public function setDefaultDisplayGroupClass($class)
	{
		$this->_defaultDisplayGroupClass = (string) $class;
		return $this;
	}

	/**
	 * Retrieve default display group class
	 *
	 * @return string
	 */
	public function getDefaultDisplayGroupClass()
	{
		return $this->_defaultDisplayGroupClass;
	}

	/**
	 * Add a display group
	 *
	 * Groups named elements for display purposes.
	 *
	 * If a referenced element does not yet exist in the form, it is omitted.
	 *
	 * @param  array $elements
	 * @param  string $name
	 * @param  array|Zend_Config $options
	 * @return Zend_Form
	 * @throws Zend_Form_Exception if no valid elements provided
	 */
	public function addDisplayGroup(array $elements, $name, $options = null)
	{
		$group = array();
		foreach ($elements as $element) {
			if (isset($this->_elements[$element])) {
				$add = $this->getElement($element);
				if (null !== $add) {
					unset($this->_order[$element]);
					$group[] = $add;
				}
			}
		}
		if (empty($group)) {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception('No valid elements specified for display group');
		}

		$name = (string) $name;

		if (is_array($options)) {
			$options['elements'] = $group;
		} elseif ($options instanceof Zend_Config) {
			$options = $options->toArray();
			$options['elements'] = $group;
		} else {
			$options = array('elements' => $group);
		}

		if (isset($options['displayGroupClass'])) {
			$class = $options['displayGroupClass'];
			unset($options['displayGroupClass']);
		} else {
			$class = $this->getDefaultDisplayGroupClass();
		}

		if (!class_exists($class)) {
			require_once 'Zend/Loader.php';
			Zend_Loader::loadClass($class);
		}
		$this->_displayGroups[$name] = new $class(
		$name,
		$this->getPluginLoader(self::DECORATOR),
		$options
		);

		if (!empty($this->_displayGroupPrefixPaths)) {
			$this->_displayGroups[$name]->addPrefixPaths($this->_displayGroupPrefixPaths);
		}

		$this->_order[$name] = $this->_displayGroups[$name]->getOrder();
		$this->_orderUpdated = true;
		return $this;
	}

	/**
	 * Add a display group object (used with cloning)
	 *
	 * @param  Zend_Form_DisplayGroup $group
	 * @param  string|null $name
	 * @return Zend_Form
	 */
	protected function _addDisplayGroupObject(Zend_Form_DisplayGroup $group, $name = null)
	{
		if (null === $name) {
			$name = $group->getName();
			if (empty($name)) {
				require_once 'Zend/Form/Exception.php';
				throw new Zend_Form_Exception('Invalid display group added; requires name');
			}
		}

		$this->_displayGroups[$name] = $group;

		if (!empty($this->_displayGroupPrefixPaths)) {
			$this->_displayGroups[$name]->addPrefixPaths($this->_displayGroupPrefixPaths);
		}

		$this->_order[$name] = $this->_displayGroups[$name]->getOrder();
		$this->_orderUpdated = true;
		return $this;
	}

	/**
	 * Add multiple display groups at once
	 *
	 * @param  array $groups
	 * @return Zend_Form
	 */
	public function addDisplayGroups(array $groups)
	{
		foreach ($groups as $key => $spec) {
			$name = null;
			if (!is_numeric($key)) {
				$name = $key;
			}

			if ($spec instanceof Zend_Form_DisplayGroup) {
				$this->_addDisplayGroupObject($spec);
			}

			if (!is_array($spec) || empty($spec)) {
				continue;
			}

			$argc    = count($spec);
			$options = array();

			if (isset($spec['elements'])) {
				$elements = $spec['elements'];
				if (isset($spec['name'])) {
					$name = $spec['name'];
				}
				if (isset($spec['options'])) {
					$options = $spec['options'];
				}
				$this->addDisplayGroup($elements, $name, $options);
			} else {
				switch ($argc) {
					case (1 <= $argc):
						$elements = array_shift($spec);
						if (!is_array($elements) && (null !== $name)) {
							$elements = array_merge((array) $elements, $spec);
							$this->addDisplayGroup($elements, $name);
							break;
						}
					case (2 <= $argc):
						if (null !== $name) {
							$options = array_shift($spec);
							$this->addDisplayGroup($elements, $name, $options);
							break;
						}
						$name = array_shift($spec);
					case (3 <= $argc):
						$options = array_shift($spec);
					default:
						$this->addDisplayGroup($elements, $name, $options);
				}
			}
		}
		return $this;
	}

	/**
	 * Add multiple display groups (overwrites)
	 *
	 * @param  array $groups
	 * @return Zend_Form
	 */
	public function setDisplayGroups(array $groups)
	{
		return $this->clearDisplayGroups()
		->addDisplayGroups($groups);
	}

	/**
	 * Return a display group
	 *
	 * @param  string $name
	 * @return Zend_Form_DisplayGroup|null
	 */
	public function getDisplayGroup($name)
	{
		$name = (string) $name;
		if (isset($this->_displayGroups[$name])) {
			return $this->_displayGroups[$name];
		}

		return null;
	}

	/**
	 * Return all display groups
	 *
	 * @return array
	 */
	public function getDisplayGroups()
	{
		return $this->_displayGroups;
	}

	/**
	 * Remove a display group by name
	 *
	 * @param  string $name
	 * @return boolean
	 */
	public function removeDisplayGroup($name)
	{
		$name = (string) $name;
		if (array_key_exists($name, $this->_displayGroups)) {
			foreach ($this->_displayGroups[$name] as $key => $element) {
				if (array_key_exists($key, $this->_elements)) {
					$this->_order[$key]  = $element->getOrder();
					$this->_orderUpdated = true;
				}
			}
			unset($this->_displayGroups[$name]);

			if (array_key_exists($name, $this->_order)) {
				unset($this->_order[$name]);
				$this->_orderUpdated = true;
			}
			return true;
		}

		return false;
	}

	/**
	 * Remove all display groups
	 *
	 * @return Zend_Form
	 */
	public function clearDisplayGroups()
	{
		foreach ($this->_displayGroups as $key => $group) {
			if (array_key_exists($key, $this->_order)) {
				unset($this->_order[$key]);
			}
			foreach ($group as $name => $element) {
				if (isset($this->_elements[$name])) {
					$this->_order[$name] = $element->getOrder();
				}
				$this->_order[$name] = $element->getOrder();
			}
		}
		$this->_displayGroups = array();
		$this->_orderUpdated  = true;
		return $this;
	}


	// Processing

	/**
	 * Populate form
	 *
	 * Proxies to {@link setDefaults()}
	 *
	 * @param  array $values
	 * @return Zend_Form
	 */
	public function populate(array $values)
	{
		return $this->setDefaults($values);
	}

	/**
	 * Determine array key name from given value
	 *
	 * Given a value such as foo[bar][baz], returns the last element (in this case, 'baz').
	 *
	 * @param  string $value
	 * @return string
	 */
	protected function _getArrayName($value)
	{
		if (empty($value) || !is_string($value)) {
			return $value;
		}

		if (!strstr($value, '[')) {
			return $value;
		}

		$endPos = strlen($value) - 1;
		if (']' != $value[$endPos]) {
			return $value;
		}

		$start = strrpos($value, '[') + 1;
		$name = substr($value, $start, $endPos - $start);
		return $name;
	}

	/**
	 * Extract the value by walking the array using given array path.
	 *
	 * Given an array path such as foo[bar][baz], returns the value of the last
	 * element (in this case, 'baz').
	 *
	 * @param  array $value Array to walk
	 * @param  string $arrayPath Array notation path of the part to extract
	 * @return string
	 */
	protected function _dissolveArrayValue($value, $arrayPath)
	{
		// As long as we have more levels
		while ($arrayPos = strpos($arrayPath, '[')) {
			// Get the next key in the path
			$arrayKey = trim(substr($arrayPath, 0, $arrayPos), ']');

			// Set the potentially final value or the next search point in the array
			if (isset($value[$arrayKey])) {
				$value = $value[$arrayKey];
			}

			// Set the next search point in the path
			$arrayPath = trim(substr($arrayPath, $arrayPos + 1), ']');
		}

		if (isset($value[$arrayPath])) {
			$value = $value[$arrayPath];
		}

		return $value;
	}

	/**
	 * Converts given arrayPath to an array and attaches given value at the end of it.
	 *
	 * @param  mixed $value The value to attach
	 * @param  string $arrayPath Given array path to convert and attach to.
	 * @return array
	 */
	protected function _attachToArray($value, $arrayPath)
	{
		// As long as we have more levels
		while ($arrayPos = strrpos($arrayPath, '[')) {
			// Get the next key in the path
			$arrayKey = trim(substr($arrayPath, $arrayPos + 1), ']');

			// Attach
			$value = array($arrayKey => $value);

			// Set the next search point in the path
			$arrayPath = trim(substr($arrayPath, 0, $arrayPos), ']');
		}

		$value = array($arrayPath => $value);

		return $value;
	}

	/**
	 * Validate the form
	 *
	 * @param  array $data
	 * @return boolean
	 */
	public function isValid($data)
	{
		if (!is_array($data)) {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception(__CLASS__ . '::' . __METHOD__ . ' expects an array');
		}
		$translator = $this->getTranslator();
		$valid      = true;

		if ($this->isArray()) {
			$data = $this->_dissolveArrayValue($data, $this->getElementsBelongTo());
		}

		foreach ($this->getElements() as $key => $element) {
			$element->setTranslator($translator);
			if (!isset($data[$key])) {
				$valid = $element->isValid(null, $data) && $valid;
			} else {
				$valid = $element->isValid($data[$key], $data) && $valid;
			}
		}
		foreach ($this->getSubForms() as $key => $form) {
			$form->setTranslator($translator);
			if (isset($data[$key])) {
				$valid = $form->isValid($data[$key]) && $valid;
			} else {
				$valid = $form->isValid($data) && $valid;
			}
		}

		$this->_errorsExist = !$valid;

		// If manually flagged as an error, return invalid status
		if ($this->_errorsForced) {
			return false;
		}

		return $valid;
	}

	/**
	 * Validate a partial form
	 *
	 * Does not check for required flags.
	 *
	 * @param  array $data
	 * @return boolean
	 */
	public function isValidPartial(array $data)
	{
		if ($this->isArray()) {
			$data = $this->_dissolveArrayValue($data, $this->getElementsBelongTo());
		}

		$translator        = $this->getTranslator();
		$valid             = true;
		$validatedSubForms = array();

		foreach ($data as $key => $value) {
			if (null !== ($element = $this->getElement($key))) {
				if (null !== $translator) {
					$element->setTranslator($translator);
				}
				$valid = $element->isValid($value, $data) && $valid;
			} elseif (null !== ($subForm = $this->getSubForm($key))) {
				if (null !== $translator) {
					$subForm->setTranslator($translator);
				}
				$valid = $subForm->isValidPartial($data[$key]) && $valid;
				$validatedSubForms[] = $key;
			}
		}
		foreach ($this->getSubForms() as $key => $subForm) {
			if (!in_array($key, $validatedSubForms)) {
				if (null !== $translator) {
					$subForm->setTranslator($translator);
				}

				$valid = $subForm->isValidPartial($data) && $valid;
			}
		}

		$this->_errorsExist = !$valid;
		return $valid;
	}

	/**
	 * Process submitted AJAX data
	 *
	 * Checks if provided $data is valid, via {@link isValidPartial()}. If so,
	 * it returns JSON-encoded boolean true. If not, it returns JSON-encoded
	 * error messages (as returned by {@link getMessages()}).
	 *
	 * @param  array $data
	 * @return string JSON-encoded boolean true or error messages
	 */
	public function processAjax(array $data)
	{
		require_once 'Zend/Json.php';
		if ($this->isValidPartial($data)) {
			return Zend_Json::encode(true);
		}
		$messages = $this->getMessages();
		return Zend_Json::encode($messages);
	}

	/**
	 * Add a custom error message to return in the event of failed validation
	 *
	 * @param  string $message
	 * @return Zend_Form
	 */
	public function addErrorMessage($message)
	{
		$this->_errorMessages[] = (string) $message;
		return $this;
	}

	/**
	 * Add multiple custom error messages to return in the event of failed validation
	 *
	 * @param  array $messages
	 * @return Zend_Form
	 */
	public function addErrorMessages(array $messages)
	{
		foreach ($messages as $message) {
			$this->addErrorMessage($message);
		}
		return $this;
	}

	/**
	 * Same as addErrorMessages(), but clears custom error message stack first
	 *
	 * @param  array $messages
	 * @return Zend_Form
	 */
	public function setErrorMessages(array $messages)
	{
		$this->clearErrorMessages();
		return $this->addErrorMessages($messages);
	}

	/**
	 * Retrieve custom error messages
	 *
	 * @return array
	 */
	public function getErrorMessages()
	{
		return $this->_errorMessages;
	}

	/**
	 * Clear custom error messages stack
	 *
	 * @return Zend_Form
	 */
	public function clearErrorMessages()
	{
		$this->_errorMessages = array();
		return $this;
	}

	/**
	 * Mark the element as being in a failed validation state
	 *
	 * @return Zend_Form
	 */
	public function markAsError()
	{
		$this->_errorsExist  = true;
		$this->_errorsForced = true;
		return $this;
	}

	/**
	 * Add an error message and mark element as failed validation
	 *
	 * @param  string $message
	 * @return Zend_Form
	 */
	public function addError($message)
	{
		$this->addErrorMessage($message);
		$this->markAsError();
		return $this;
	}

	/**
	 * Add multiple error messages and flag element as failed validation
	 *
	 * @param  array $messages
	 * @return Zend_Form
	 */
	public function addErrors(array $messages)
	{
		foreach ($messages as $message) {
			$this->addError($message);
		}
		return $this;
	}

	/**
	 * Overwrite any previously set error messages and flag as failed validation
	 *
	 * @param  array $messages
	 * @return Zend_Form
	 */
	public function setErrors(array $messages)
	{
		$this->clearErrorMessages();
		return $this->addErrors($messages);
	}


	public function persistData()
	{
	}

	/**
	 * Are there errors in the form?
	 *
	 * @return bool
	 */
	public function isErrors()
	{
		return $this->_errorsExist;
	}

	/**
	 * Get error codes for all elements failing validation
	 *
	 * @param  string $name
	 * @return array
	 */
	public function getErrors($name = null)
	{
		$errors = array();
		if ((null !== $name) && isset($this->_elements[$name])) {
			$errors = $this->getElement($name)->getErrors();
		} elseif ((null !== $name) && isset($this->_subForms[$name])) {
			$errors = $this->getSubForm($name)->getErrors();
		} else {
			foreach ($this->_elements as $key => $element) {
				$errors[$key] = $element->getErrors();
			}
			foreach ($this->getSubForms() as $key => $subForm) {
				$fErrors = $this->_attachToArray($subForm->getErrors(), $subForm->getElementsBelongTo());
				$errors = array_merge($errors, $fErrors);
			}
		}
		return $errors;
	}

	/**
	 * Retrieve error messages from elements failing validations
	 *
	 * @param  string $name
	 * @param  bool $suppressArrayNotation
	 * @return array
	 */
	public function getMessages($name = null, $suppressArrayNotation = false)
	{
		if ((null !== $name) && isset($this->_elements[$name])) {
			return $this->getElement($name)->getMessages();
		}

		if ((null !== $name) && isset($this->_subForms[$name])) {
			return $this->getSubForm($name)->getMessages(null, true);
		}

		$arrayKeys = array();
		foreach ($this->getSubForms() as $key => $subForm) {
			$array = $this->_getArrayName($subForm->getElementsBelongTo());
			if (!empty($array)) {
				if ($name == $array) {
					return $subForm->getMessages(null, true);
				}
				$arrayKeys[$key] = $subForm->getElementsBelongTo();
			}
		}

		$customMessages = $this->_getErrorMessages();
		if ($this->isErrors() && !empty($customMessages)) {
			return $customMessages;
		}

		$messages = array();

		foreach ($this->getElements() as $name => $element) {
			$eMessages = $element->getMessages();
			if (!empty($eMessages)) {
				$messages[$name] = $eMessages;
			}
		}

		foreach ($this->getSubForms() as $key => $subForm) {
			$fMessages = $subForm->getMessages(null, true);
			if (!empty($fMessages)) {
				if (array_key_exists($key, $arrayKeys)) {
					$fMessages = $this->_attachToArray($fMessages, $arrayKeys[$key]);
					$messages = array_merge($messages, $fMessages);
				} else {
					$messages[$key] = $fMessages;
				}
			}
		}

		if (!$suppressArrayNotation && $this->isArray()) {
			$messages = $this->_attachToArray($messages, $this->getElementsBelongTo());
		}

		return $messages;
	}


	// Rendering

	/**
	 * Set view object
	 *
	 * @param  Zend_View_Interface $view
	 * @return Zend_Form
	 */
	public function setView(Zend_View_Interface $view = null)
	{
		$this->_view = $view;
		return $this;
	}

	/**
	 * Retrieve view object
	 *
	 * If none registered, attempts to pull from ViewRenderer.
	 *
	 * @return Zend_View_Interface|null
	 */
	public function getView()
	{
		if (null === $this->_view) {
			require_once 'Zend/Controller/Action/HelperBroker.php';
			$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
			$this->setView($viewRenderer->view);
		}

		return $this->_view;
	}

	/**
	 * Instantiate a decorator based on class name or class name fragment
	 *
	 * @param  string $name
	 * @param  null|array $options
	 * @return Zend_Form_Decorator_Interface
	 */
	protected function _getDecorator($name, $options)
	{
		$class = $this->getPluginLoader(self::DECORATOR)->load($name);
		if (null === $options) {
			$decorator = new $class;
		} else {
			$decorator = new $class($options);
		}

		return $decorator;
	}

	/**
	 * Add a decorator for rendering the element
	 *
	 * @param  string|Zend_Form_Decorator_Interface $decorator
	 * @param  array|Zend_Config $options Options with which to initialize decorator
	 * @return Zend_Form
	 */
	public function addDecorator($decorator, $options = null)
	{
		if ($decorator instanceof Zend_Form_Decorator_Interface) {
			$name = get_class($decorator);
		} elseif (is_string($decorator)) {
			$name      = $decorator;
			$decorator = array(
                'decorator' => $name,
                'options'   => $options,
			);
		} elseif (is_array($decorator)) {
			foreach ($decorator as $name => $spec) {
				break;
			}
			if (is_numeric($name)) {
				require_once 'Zend/Form/Exception.php';
				throw new Zend_Form_Exception('Invalid alias provided to addDecorator; must be alphanumeric string');
			}
			if (is_string($spec)) {
				$decorator = array(
                    'decorator' => $spec,
                    'options'   => $options,
				);
			} elseif ($spec instanceof Zend_Form_Decorator_Interface) {
				$decorator = $spec;
			}
		} else {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception('Invalid decorator provided to addDecorator; must be string or Zend_Form_Decorator_Interface');
		}

		$this->_decorators[$name] = $decorator;

		return $this;
	}

	/**
	 * Add many decorators at once
	 *
	 * @param  array $decorators
	 * @return Zend_Form
	 */
	public function addDecorators(array $decorators)
	{
		foreach ($decorators as $decoratorInfo) {
			if (is_string($decoratorInfo)) {
				$this->addDecorator($decoratorInfo);
			} elseif ($decoratorInfo instanceof Zend_Form_Decorator_Interface) {
				$this->addDecorator($decoratorInfo);
			} elseif (is_array($decoratorInfo)) {
				$argc    = count($decoratorInfo);
				$options = array();
				if (isset($decoratorInfo['decorator'])) {
					$decorator = $decoratorInfo['decorator'];
					if (isset($decoratorInfo['options'])) {
						$options = $decoratorInfo['options'];
					}
					$this->addDecorator($decorator, $options);
				} else {
					switch (true) {
						case (0 == $argc):
							break;
						case (1 <= $argc):
							$decorator  = array_shift($decoratorInfo);
						case (2 <= $argc):
							$options = array_shift($decoratorInfo);
						default:
							$this->addDecorator($decorator, $options);
							break;
					}
				}
			} else {
				require_once 'Zend/Form/Exception.php';
				throw new Zend_Form_Exception('Invalid decorator passed to addDecorators()');
			}
		}

		return $this;
	}

	/**
	 * Overwrite all decorators
	 *
	 * @param  array $decorators
	 * @return Zend_Form
	 */
	public function setDecorators(array $decorators)
	{
		$this->clearDecorators();
		return $this->addDecorators($decorators);
	}

	/**
	 * Retrieve a registered decorator
	 *
	 * @param  string $name
	 * @return false|Zend_Form_Decorator_Abstract
	 */
	public function getDecorator($name)
	{
		if (!isset($this->_decorators[$name])) {
			$len = strlen($name);
			foreach ($this->_decorators as $localName => $decorator) {
				if ($len > strlen($localName)) {
					continue;
				}

				if (0 === substr_compare($localName, $name, -$len, $len, true)) {
					if (is_array($decorator)) {
						return $this->_loadDecorator($decorator, $localName);
					}
					return $decorator;
				}
			}
			return false;
		}

		if (is_array($this->_decorators[$name])) {
			return $this->_loadDecorator($this->_decorators[$name], $name);
		}

		return $this->_decorators[$name];
	}

	/**
	 * Retrieve all decorators
	 *
	 * @return array
	 */
	public function getDecorators()
	{
		foreach ($this->_decorators as $key => $value) {
			if (is_array($value)) {
				$this->_loadDecorator($value, $key);
			}
		}
		return $this->_decorators;
	}

	/**
	 * Remove a single decorator
	 *
	 * @param  string $name
	 * @return bool
	 */
	public function removeDecorator($name)
	{
		$decorator = $this->getDecorator($name);
		if ($decorator) {
			if (array_key_exists($name, $this->_decorators)) {
				unset($this->_decorators[$name]);
			} else {
				$class = get_class($decorator);
				if (!array_key_exists($class, $this->_decorators)) {
					return false;
				}
				unset($this->_decorators[$class]);
			}
			return true;
		}

		return false;
	}

	/**
	 * Clear all decorators
	 *
	 * @return Zend_Form
	 */
	public function clearDecorators()
	{
		$this->_decorators = array();
		return $this;
	}

	/**
	 * Set all element decorators as specified
	 *
	 * @param  array $decorators
	 * @param  array|null $elements Specific elements to decorate or exclude from decoration
	 * @param  bool $include Whether $elements is an inclusion or exclusion list
	 * @return Zend_Form
	 */
	public function setElementDecorators(array $decorators, array $elements = null, $include = true)
	{
		if (is_array($elements)) {
			if ($include) {
				$elementObjs = array();
				foreach ($elements as $name) {
					if (null !== ($element = $this->getElement($name))) {
						$elementObjs[] = $element;
					}
				}
			} else {
				$elementObjs = $this->getElements();
				foreach ($elements as $name) {
					if (array_key_exists($name, $elementObjs)) {
						unset($elementObjs[$name]);
					}
				}
			}
		} else {
			$elementObjs = $this->getElements();
		}

		foreach ($elementObjs as $element) {
			$element->setDecorators($decorators);
		}

		$this->_elementDecorators = $decorators;

		return $this;
	}

	/**
	 * Set all display group decorators as specified
	 *
	 * @param  array $decorators
	 * @return Zend_Form
	 */
	public function setDisplayGroupDecorators(array $decorators)
	{
		foreach ($this->getDisplayGroups() as $group) {
			$group->setDecorators($decorators);
		}

		return $this;
	}

	/**
	 * Set all subform decorators as specified
	 *
	 * @param  array $decorators
	 * @return Zend_Form
	 */
	public function setSubFormDecorators(array $decorators)
	{
		foreach ($this->getSubForms() as $form) {
			$form->setDecorators($decorators);
		}

		return $this;
	}

	/**
	 * Render form
	 *
	 * @param  Zend_View_Interface $view
	 * @return string
	 */
	public function render(Zend_View_Interface $view = null)
	{
		if (null !== $view) {
			$this->setView($view);
		}

		$content = '';
		foreach ($this->getDecorators() as $decorator) {
			$decorator->setElement($this);
			$content = $decorator->render($content);
		}
		return $content;
	}

	/**
	 * Serialize as string
	 *
	 * Proxies to {@link render()}.
	 *
	 * @return string
	 */
	public function __toString()
	{
		try {
			$return = $this->render();
			return $return;
		} catch (Exception $e) {
			$message = "Exception caught by form: " . $e->getMessage()
			. "\nStack Trace:\n" . $e->getTraceAsString();
			trigger_error($message, E_USER_WARNING);
			return '';
		}
	}


	// Localization:

	/**
	 * Set translator object
	 *
	 * @param  Zend_Translate|Zend_Translate_Adapter|null $translator
	 * @return Zend_Form
	 */
	public function setTranslator($translator = null)
	{
		if (null === $translator) {
			$this->_translator = null;
		} elseif ($translator instanceof Zend_Translate_Adapter) {
			$this->_translator = $translator;
		} elseif ($translator instanceof Zend_Translate) {
			$this->_translator = $translator->getAdapter();
		} else {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception('Invalid translator specified');
		}

		return $this;
	}

	/**
	 * Set global default translator object
	 *
	 * @param  Zend_Translate|Zend_Translate_Adapter|null $translator
	 * @return void
	 */
	public static function setDefaultTranslator($translator = null)
	{
		if (null === $translator) {
			self::$_translatorDefault = null;
		} elseif ($translator instanceof Zend_Translate_Adapter) {
			self::$_translatorDefault = $translator;
		} elseif ($translator instanceof Zend_Translate) {
			self::$_translatorDefault = $translator->getAdapter();
		} else {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception('Invalid translator specified');
		}
	}

	/**
	 * Retrieve translator object
	 *
	 * @return Zend_Translate|null
	 */
	public function getTranslator()
	{
		if ($this->translatorIsDisabled()) {
			return null;
		}

		if (null === $this->_translator) {
			return self::getDefaultTranslator();
		}

		return $this->_translator;
	}

	/**
	 * Get global default translator object
	 *
	 * @return null|Zend_Translate
	 */
	public static function getDefaultTranslator()
	{
		if (null === self::$_translatorDefault) {
			require_once 'Zend/Registry.php';
			if (Zend_Registry::isRegistered('Zend_Translate')) {
				$translator = Zend_Registry::get('Zend_Translate');
				if ($translator instanceof Zend_Translate_Adapter) {
					return $translator;
				} elseif ($translator instanceof Zend_Translate) {
					return $translator->getAdapter();
				}
			}
		}
		return self::$_translatorDefault;
	}

	/**
	 * Indicate whether or not translation should be disabled
	 *
	 * @param  bool $flag
	 * @return Zend_Form
	 */
	public function setDisableTranslator($flag)
	{
		$this->_translatorDisabled = (bool) $flag;
		return $this;
	}

	/**
	 * Is translation disabled?
	 *
	 * @return bool
	 */
	public function translatorIsDisabled()
	{
		return $this->_translatorDisabled;
	}

	/**
	 * Overloading: access to elements, form groups, and display groups
	 *
	 * @param  string $name
	 * @return Zend_Form_Element|Zend_Form|null
	 */
	public function __get($name)
	{
		if (isset($this->_elements[$name])) {
			return $this->_elements[$name];
		} elseif (isset($this->_subForms[$name])) {
			return $this->_subForms[$name];
		} elseif (isset($this->_displayGroups[$name])) {
			return $this->_displayGroups[$name];
		}

		return null;
	}

	/**
	 * Overloading: access to elements, form groups, and display groups
	 *
	 * @param  string $name
	 * @param  Zend_Form_Element|Zend_Form $value
	 * @return void
	 * @throws Zend_Form_Exception for invalid $value
	 */
	public function __set($name, $value)
	{
		if ($value instanceof Zend_Form_Element) {
			$this->addElement($value, $name);
			return;
		} elseif ($value instanceof Zend_Form) {
			$this->addSubForm($value, $name);
			return;
		} elseif (is_array($value)) {
			$this->addDisplayGroup($value, $name);
			return;
		}

		require_once 'Zend/Form/Exception.php';
		if (is_object($value)) {
			$type = get_class($value);
		} else {
			$type = gettype($value);
		}
		throw new Zend_Form_Exception('Only form elements and groups may be overloaded; variable of type "' . $type . '" provided');
	}

	/**
	 * Overloading: access to elements, form groups, and display groups
	 *
	 * @param  string $name
	 * @return boolean
	 */
	public function __isset($name)
	{
		if (isset($this->_elements[$name])
		|| isset($this->_subForms[$name])
		|| isset($this->_displayGroups[$name]))
		{
			return true;
		}

		return false;
	}

	/**
	 * Overloading: access to elements, form groups, and display groups
	 *
	 * @param  string $name
	 * @return void
	 */
	public function __unset($name)
	{
		if (isset($this->_elements[$name])) {
			unset($this->_elements[$name]);
		} elseif (isset($this->_subForms[$name])) {
			unset($this->_subForms[$name]);
		} elseif (isset($this->_displayGroups[$name])) {
			unset($this->_displayGroups[$name]);
		}
	}

	/**
	 * Overloading: allow rendering specific decorators
	 *
	 * Call renderDecoratorName() to render a specific decorator.
	 *
	 * @param  string $method
	 * @param  array $args
	 * @return string
	 * @throws Zend_Form_Exception for invalid decorator or invalid method call
	 */
	public function __call($method, $args)
	{
		if ('render' == substr($method, 0, 6)) {
			$decoratorName = substr($method, 6);
			if (false !== ($decorator = $this->getDecorator($decoratorName))) {
				$decorator->setElement($this);
				$seed = '';
				if (0 < count($args)) {
					$seed = array_shift($args);
				}
				return $decorator->render($seed);
			}

			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception(sprintf('Decorator by name %s does not exist', $decoratorName));
		}

		require_once 'Zend/Form/Exception.php';
		throw new Zend_Form_Exception(sprintf('Method %s does not exist', $method));
	}

	// Interfaces: Iterator, Countable

	/**
	 * Current element/subform/display group
	 *
	 * @return Zend_Form_Element|Zend_Form_DisplayGroup|Zend_Form
	 */
	public function current()
	{
		$this->_sort();
		current($this->_order);
		$key = key($this->_order);

		if (isset($this->_elements[$key])) {
			return $this->getElement($key);
		} elseif (isset($this->_subForms[$key])) {
			return $this->getSubForm($key);
		} elseif (isset($this->_displayGroups[$key])) {
			return $this->getDisplayGroup($key);
		} else {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception(sprintf('Corruption detected in form; invalid key ("%s") found in internal iterator', (string) $key));
		}
	}

	/**
	 * Current element/subform/display group name
	 *
	 * @return string
	 */
	public function key()
	{
		$this->_sort();
		return key($this->_order);
	}

	/**
	 * Move pointer to next element/subform/display group
	 *
	 * @return void
	 */
	public function next()
	{
		$this->_sort();
		next($this->_order);
	}

	/**
	 * Move pointer to beginning of element/subform/display group loop
	 *
	 * @return void
	 */
	public function rewind()
	{
		$this->_sort();
		reset($this->_order);
	}

	/**
	 * Determine if current element/subform/display group is valid
	 *
	 * @return bool
	 */
	public function valid()
	{
		$this->_sort();
		return (current($this->_order) !== false);
	}

	/**
	 * Count of elements/subforms that are iterable
	 *
	 * @return int
	 */
	public function count()
	{
		return count($this->_order);
	}

	/**
	 * Set flag to disable loading default decorators
	 *
	 * @param  bool $flag
	 * @return Zend_Form
	 */
	public function setDisableLoadDefaultDecorators($flag)
	{
		$this->_disableLoadDefaultDecorators = (bool) $flag;
		return $this;
	}

	/**
	 * Should we load the default decorators?
	 *
	 * @return bool
	 */
	public function loadDefaultDecoratorsIsDisabled()
	{
		return $this->_disableLoadDefaultDecorators;
	}

	/**
	 * Load the default decorators
	 *
	 * @return void
	 */
	public function loadDefaultDecorators()
	{
		if ($this->loadDefaultDecoratorsIsDisabled()) {
			return;
		}

		$decorators = $this->getDecorators();
		if (empty($decorators)) {
			$this->addDecorator('FormElements')
			->addDecorator('HtmlTag', array('tag' => 'dl', 'class' => 'zend_form'))
			->addDecorator('Form');
		}
	}

	/**
	 * Sort items according to their order
	 *
	 * @return void
	 */
	protected function _sort()
	{
		if ($this->_orderUpdated) {
			$items = array();
			$index = 0;
			foreach ($this->_order as $key => $order) {
				if (null === $order) {
					if (null === ($order = $this->{$key}->getOrder())) {
						while (array_search($index, $this->_order, true)) {
							++$index;
						}
						$items[$index] = $key;
						++$index;
					} else {
						$items[$order] = $key;
					}
				} else {
					$items[$order] = $key;
				}
			}

			$items = array_flip($items);
			asort($items);
			$this->_order = $items;
			$this->_orderUpdated = false;
		}
	}

	/**
	 * Lazy-load a decorator
	 *
	 * @param  array $decorator Decorator type and options
	 * @param  mixed $name Decorator name or alias
	 * @return Zend_Form_Decorator_Interface
	 */
	protected function _loadDecorator(array $decorator, $name)
	{
		$sameName = false;
		if ($name == $decorator['decorator']) {
			$sameName = true;
		}

		$instance = $this->_getDecorator($decorator['decorator'], $decorator['options']);
		if ($sameName) {
			$newName            = get_class($instance);
			$decoratorNames     = array_keys($this->_decorators);
			$order              = array_flip($decoratorNames);
			$order[$newName]    = $order[$name];
			$decoratorsExchange = array();
			unset($order[$name]);
			asort($order);
			foreach ($order as $key => $index) {
				if ($key == $newName) {
					$decoratorsExchange[$key] = $instance;
					continue;
				}
				$decoratorsExchange[$key] = $this->_decorators[$key];
			}
			$this->_decorators = $decoratorsExchange;
		} else {
			$this->_decorators[$name] = $instance;
		}

		return $instance;
	}

	/**
	 * Retrieve optionally translated custom error messages
	 *
	 * @return array
	 */
	protected function _getErrorMessages()
	{
		$messages   = $this->getErrorMessages();
		$translator = $this->getTranslator();
		if (null !== $translator) {
			foreach ($messages as $key => $message) {
				$messages[$key] = $translator->translate($message);
			}
		}
		return $messages;
	}
}
